package com.boornet.encrypt.util;

import java.io.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Scanner;

import javax.crypto.Cipher;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * 非对称加密RSA
 * @author zcx
 *
 */
class RSAUtils {

    /** 算法名称 */
    private static final String ALGORITHM = "RSA";

    /** 密钥长度 */
    private static final int KEY_SIZE = 2048;

    /**
     * 公钥的持久化文件
     */
    private static String PUBLIC_KEY_FILE = "publicKey.txt";

    /**
     * 私钥的持久化文件
     */
    private static String PRIVATE_KEY_FILE = "privateKey.txt";

    /**
     * 随机生成密钥对（包含公钥和私钥）
     */
    public static KeyPair generateKeyPair() throws Exception {
        // 获取指定算法的密钥对生成器
        KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);

        // 初始化密钥对生成器（指定密钥长度, 使用默认的安全随机数源）
        gen.initialize(KEY_SIZE);

        // 随机生成一对密钥（包含公钥和私钥）
        return gen.generateKeyPair();
    }

    /**
     * 将 公钥/私钥 编码后以 Base64 的格式保存到指定文件
     */
    public static void saveKeyForEncodedBase64(Key key, File keyFile) throws IOException {
        // 获取密钥编码后的格式
        byte[] encBytes = key.getEncoded();

        // 转换为 Base64 文本
        String encBase64 = new BASE64Encoder().encode(encBytes);

        // 保存到文件
        IOUtils.writeFile(encBase64, keyFile);
    }

    /**
     * 根据公钥的 Base64 文本创建公钥对象
     */
    public static PublicKey getPublicKey(String pubKeyBase64) throws Exception {
        // 把 公钥的Base64文本 转换为已编码的 公钥bytes
        byte[] encPubKey = new BASE64Decoder().decodeBuffer(pubKeyBase64);

        // 创建 已编码的公钥规格
        X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(encPubKey);

        // 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
        return KeyFactory.getInstance(ALGORITHM).generatePublic(encPubKeySpec);
    }

    /**
     * 根据私钥的 Base64 文本创建私钥对象
     */
    public static PrivateKey getPrivateKey(String priKeyBase64) throws Exception {
        // 把 私钥的Base64文本 转换为已编码的 私钥bytes
        byte[] encPriKey = new BASE64Decoder().decodeBuffer(priKeyBase64);

        // 创建 已编码的私钥规格
        PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(encPriKey);

        // 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象
        return KeyFactory.getInstance(ALGORITHM).generatePrivate(encPriKeySpec);
    }

    /**
     * 公钥加密数据
     */
    public static byte[] encrypt(byte[] plainData, PublicKey pubKey) throws Exception {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);

        // 初始化密码器（公钥加密模型）
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);

        // 加密数据, 返回加密后的密文
        return cipher.doFinal(plainData);
    }

    /**
     * 私钥解密数据
     */
    public static byte[] decrypt(byte[] cipherData, PrivateKey priKey) throws Exception {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);

        // 初始化密码器（私钥解密模型）
        cipher.init(Cipher.DECRYPT_MODE, priKey);

        // 解密数据, 返回解密后的明文
        return cipher.doFinal(cipherData);
    }

    /**
     * 客户端加密, 返回加密后的数据
     */
    private static byte[] clientEncrypt(byte[] plainData, File pubFile) throws Exception {
        // 读取公钥文件, 创建公钥对象
        PublicKey pubKey = getPublicKey(IOUtils.readFile(pubFile));

        // 用公钥加密数据
        byte[] cipher = encrypt(plainData, pubKey);

        return cipher;
    }

    /**
     * 服务端解密, 返回解密后的数据
     */
    private static byte[] serverDecrypt(byte[] cipherData, File priFile) throws Exception {
        // 读取私钥文件, 创建私钥对象
        PrivateKey priKey = getPrivateKey(IOUtils.readFile(priFile));

        // 用私钥解密数据
        byte[] plainData = decrypt(cipherData, priKey);

        return plainData;
    }

    public static void main(String[] args) throws Exception{

//        String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLZRs4uSQTkqU7BhTCx9zcveNK82oqN1fUokV9KvTmsKQZHSFkg0LCnua/aXbWS/lCHm8bDyX+uQwhDS4Um4sjbaXNWU6KlKjdk5ZzWsXlZyVLrC9uImWDXfycjibREtb47An9uXqFTjzeenSxlXtxh6oA5/ap/Tj4shrbxYM3AQIDAQAB";
//        // 读取公钥文件, 创建公钥对象
//        PublicKey pubKey = getPublicKey(publicKeyBase64);
//        String str = "123";
//        // 用公钥加密数据
//        byte[] cipher = encrypt(str.getBytes(), pubKey);
//        System.out.println(new BASE64Encoder().encode(cipher));


        String result = "BMh5KyKKfjmwD4bZyf9iS2lkMa3r2BbNnYCevmym+p+LWQMy3giX43VkV/vJBdcRhEy0+cnQqzSb3yK0KM+isNGq+0ngZSUDUXUOoFutVl/vRkHO6iOvsKyagM2BZ+BxA8p7L8hjaewG+RIC/OnxPqXEOFjTjRgfJQ/D7GOxE8w=";
        String privateKeyBase64 = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAItlGzi5JBOSpTsGFMLH3Ny940rzaio3V9SiRX0q9OawpBkdIWSDQsKe5r9pdtZL+UIebxsPJf65DCENLhSbiyNtpc1ZToqUqN2TlnNaxeVnJUusL24iZYNd/JyOJtES1vjsCf25eoVOPN56dLGVe3GHqgDn9qn9OPiyGtvFgzcBAgMBAAECgYBVQmks/6i+RALGIEkQfhhr98UPMeUsnx8ckog4L88NTtt/9N5Y0SRDT9fJGeOy3VM/CshgX5qYu2xcxuf5eM3X2nqAUed6mcmwW1Fb7xbSlEtTzqV6PORtktDYXCtYZYRj4PvrjCW9SW2Gx+GWNIa6Sr9dQuxrPey0pL6k6E1RlQJBAN4QC3AGZG1Hk1wq9idqXMrx0m0h4LO0rxgtll7KPCmp0wwUUMvOZa5jNwCKEeeWjIBwyzSQaMR3yZfth4pNtucCQQCgssdX8NZCBboMmCQwqf43KxmxVhV5RnKNaSCoGvhko+HEf9LGIdQ7RyXwDOFHqr9Teskapp9eRNLmBGt3KC3XAkBarCJ/c5y+ImPBVql8HqoVE/jgJt0hg72bGcI0ok8+k+FhJUp4baaHNmvXBImbDpYAPA49QFoHHwJpzstxZZoxAkAE1JrtZ83o+icXjdM4q0N7AQ6qp/3JQXbcDVbv9q+UxuBYeccqMYYhqgfX6Ws0LkvGBKDa7jtIKCV2yJc6Y7jNAkEAqd41jnZ5Q3dyCKfm9J6NDswo/S1zLCCGVRPFxkRan25Im05e5Ix8n5mjqoruvcoFidNDvcZPJG+92Y6deW3WBA==";

        // 读取私钥文件, 创建私钥对象
        PrivateKey priKey = getPrivateKey(privateKeyBase64);

        // 用私钥解密数据
//        byte[] plainData = decrypt(result.getBytes(), priKey);
        byte[] plainData = decryptByPrivateKey(priKey, result.getBytes());

        System.out.println(new String(plainData));
    }

    public static void main2(String[] args) throws Exception {
        // 随机生成一对密钥（包含公钥和私钥）
        KeyPair keyPair = generateKeyPair();
        // 获取 公钥 和 私钥
        PublicKey pubKey = keyPair.getPublic();
        PrivateKey priKey = keyPair.getPrivate();

        // 保存 公钥 和 私钥
        saveKeyForEncodedBase64(pubKey, new File(PUBLIC_KEY_FILE));
        saveKeyForEncodedBase64(priKey, new File(PRIVATE_KEY_FILE));

        /*
         * 上面代码是事先生成密钥对保存,
         * 下面代码是在实际应用中, 客户端和服务端分别拿现成的公钥和私钥加密/解密数据。
         */

        // 原文数据
        System.out.println("请输入明文：");
        Scanner sca = new Scanner(System.in);
        String data = sca.nextLine();

        // 客户端: 加密
        byte[] cipherData = clientEncrypt(data.getBytes(), new File(PUBLIC_KEY_FILE));
        System.out.println("============================");
        System.out.println("经过RSA加密后的密文为：");
        System.out.println(new BASE64Encoder().encode(cipherData));

        // 服务端: 解密
        byte[] plainData = serverDecrypt(cipherData, new File(PRIVATE_KEY_FILE));
        System.out.println("============================");
        System.out.println("经过RSA解密后的原文为：");
        System.out.println(new String(plainData));
    }

    public static byte[] decryptByPrivateKey(PrivateKey privateKey, byte[] encryptedData) throws Exception {
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(2, privateKey);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;

        for(int i = 0; inputLen - offSet > 0; offSet = i * 256) {
            byte[] cache;
            if(inputLen - offSet > 256) {
                cache = cipher.doFinal(encryptedData, offSet, 256);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }

            out.write(cache, 0, cache.length);
            ++i;
        }

        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }
}